#include <stdio.h>

#include "debug.h"
#include "disasm.h"
#include "emu.h"

static char *strcpy2(char *dest, const char *src) {
    while ((*dest = *src)) { dest++; src++; }
    return dest;
}

const char reg_name[16][4] = {
    "r0", "r1", "r2",  "r3",  "r4",  "r5", "r6", "r7",
    "r8", "r9", "r10", "r11", "r12", "sp", "lr", "pc"
};

static const char condcode[16][3] = {
    "eq", "ne", "cs", "cc", "mi", "pl", "vs", "vc",
    "hi", "ls", "ge", "lt", "gt", "le", "",   "2"
};

enum suffix {
    none_,
    XY,      // halfword signed mul
    Y,       // halfword signed mul
    S,       // Bit 20    S: alu, MUL etc
    B,       // Bit 22    B: LDR/STR, SWP
    BT,      // Bit 21    T: LDR/STR with postindex (bit 24=0)
    M,       // Bit 23-24 DA/IA/DB/IB
    H,       // H/SB/SH
    D,       // double-word insn
    L,       // Bit 22    L: LDC/STC
};

enum operand {
    none,
    REG0,  // Bits 0-3   specify register
    REG8,  // Bits 8-11  specify register
    REG12, // Bits 12-15 specify register
    REG16, // Bits 16-19 specify register
    PSR,
    SHFOP, // Bits 0-11  specify shifted reg or immediate
    IMMED, // Bits 0-11  specify rotated immediate
    MEM,
    MEMH,
    MEM16,
    MULTI,
    BRCH,  // Bits 0-23  specify branch target
    BRCHT, // Bits 0-24  specify branch target for THUMB code
    COPR,  // Bits 8-11  specify coprocessor number
    CDP, MRC, MRRC, MEMC,
    BKPT, SWI,
};

static const struct arm_insn {
    uint32_t mask;
    uint32_t value;
    char name[7];
    uint8_t suffix;
    uint8_t op1, op2, op3, op4;
} table[] = {
{0xFD70F000,0xF550F000, "pld",   0, MEM,   none,  none,  none  },
{0xFE000000,0xFA000000, "blx",   0, BRCHT, none,  none,  none  },

/* Coprocessor instructions may have any condition field */
{ 0xFF00000, 0xC400000, "mcrr",  0, COPR,  MRRC,  none,  none  },
{ 0xFF00000, 0xC500000, "mrrc",  0, COPR,  MRRC,  none,  none  },
{ 0xE100000, 0xC000000, "stc",   L, COPR,  MEMC,  none,  none  },
{ 0xE100000, 0xC100000, "ldc",   L, COPR,  MEMC,  none,  none  },
{ 0xF000010, 0xE000000, "cdp",   0, COPR,  CDP,   none,  none  },
{ 0xF100010, 0xE000010, "mcr",   0, COPR,  MRC,   none,  none  },
{ 0xF100010, 0xE100010, "mrc",   0, COPR,  MRC,   none,  none  },

/* No other instructions are valid with condition field 1111 */
{0xF0000000,0xF0000000, "???",   0, none,  none,  none,  none  },

{ 0xFE0F0F0, 0x0000090, "mul",   S, REG16, REG0,  REG8,  none  },
{ 0xFE000F0, 0x0200090, "mla",   S, REG16, REG0,  REG8,  REG12 },
{ 0xFE000F0, 0x0800090, "umull", S, REG12, REG16, REG0,  REG8  },
{ 0xFE000F0, 0x0A00090, "umlal", S, REG12, REG16, REG0,  REG8  },
{ 0xFE000F0, 0x0C00090, "smull", S, REG12, REG16, REG0,  REG8  },
{ 0xFE000F0, 0x0E00090, "smlal", S, REG12, REG16, REG0,  REG8  },
{ 0xFB00FF0, 0x1000090, "swp",   B, REG12, REG0,  MEM16, none  },
{ 0xE0000F0, 0x0000090, "???",   0, none,  none,  none,  none  },

{ 0xE1000F0, 0x00000B0, "str",   H, REG12, MEMH,  none,  none  },
{ 0xE1010F0, 0x00000D0, "ldr",   D, REG12, MEMH,  none,  none  },
{ 0xE1010F0, 0x00000F0, "str",   D, REG12, MEMH,  none,  none  },
{ 0xE1000F0, 0x01000B0, "ldr",   H, REG12, MEMH,  none,  none  },
{ 0xE1000F0, 0x01000D0, "ldr",   H, REG12, MEMH,  none,  none  },
{ 0xE1000F0, 0x01000F0, "ldr",   H, REG12, MEMH,  none,  none  },

{ 0xFBF0FFF, 0x10F0000, "mrs",   0, REG12, PSR,   none,  none  },
{ 0xFB0FFF0, 0x120F000, "msr",   0, PSR,   REG0,  none,  none  },
{ 0xFF00FF0, 0x1000050, "qadd",  0, REG12, REG0,  REG16, none  },
{ 0xFF00FF0, 0x1200050, "qsub",  0, REG12, REG0,  REG16, none  },
{ 0xFF00FF0, 0x1400050, "qdadd", 0, REG12, REG0,  REG16, none  },
{ 0xFF00FF0, 0x1600050, "qdsub", 0, REG12, REG0,  REG16, none  },
{0xFFF000F0,0xE1200070, "bkpt",  0, BKPT,  none,  none,  none  },

{ 0xFB0F000, 0x320F000, "msr",   0, PSR,   IMMED, none,  none  },

{ 0xFF00090, 0x1000080, "smla",  XY,REG16, REG0,  REG8,  REG12 },
{ 0xFF000B0, 0x1200080, "smlaw", Y, REG16, REG0,  REG8,  REG12 },
{ 0xFF0F0B0, 0x12000A0, "smulw", Y, REG16, REG0,  REG8,  none  },
{ 0xFF00090, 0x1400080, "smlal", XY,REG12, REG16, REG0,  REG8  },
{ 0xFF0F090, 0x1600080, "smul",  XY,REG16, REG0,  REG8,  none  },

{ 0xFFFFFF0, 0x12FFF10, "bx",    0, REG0,  none,  none,  none  },
{ 0xFFFFFF0, 0x12FFF20, "bxj",   0, REG0,  none,  none,  none  },
{ 0xFFFFFF0, 0x12FFF30, "blx",   0, REG0,  none,  none,  none  },

{ 0xFFF0FF0, 0x16F0F10, "clz",   0, REG12, REG0,  none,  none  },

{ 0xDE00000, 0x0000000, "and",   S, REG12, REG16, SHFOP, none  },
{ 0xDE00000, 0x0200000, "eor",   S, REG12, REG16, SHFOP, none  },
{ 0xDE00000, 0x0400000, "sub",   S, REG12, REG16, SHFOP, none  },
{ 0xDE00000, 0x0600000, "rsb",   S, REG12, REG16, SHFOP, none  },
{ 0xDE00000, 0x0800000, "add",   S, REG12, REG16, SHFOP, none  },
{ 0xDE00000, 0x0A00000, "adc",   S, REG12, REG16, SHFOP, none  },
{ 0xDE00000, 0x0C00000, "sbc",   S, REG12, REG16, SHFOP, none  },
{ 0xDE00000, 0x0E00000, "rsc",   S, REG12, REG16, SHFOP, none  },
{ 0xDF0F000, 0x1100000, "tst",   0, REG16, SHFOP, none,  none  },
{ 0xDF0F000, 0x1300000, "teq",   0, REG16, SHFOP, none,  none  },
{ 0xDF0F000, 0x1500000, "cmp",   0, REG16, SHFOP, none,  none  },
{ 0xDF0F000, 0x1700000, "cmn",   0, REG16, SHFOP, none,  none  },
{ 0xDE00000, 0x1800000, "orr",   S, REG12, REG16, SHFOP, none  },
{ 0xDEF0000, 0x1A00000, "mov",   S, REG12, SHFOP, none,  none  },
{ 0xDE00000, 0x1C00000, "bic",   S, REG12, REG16, SHFOP, none  },
{ 0xDEF0000, 0x1E00000, "mvn",   S, REG12, SHFOP, none,  none  },

/* 4000000-9FFFFFF: word-sized memory accesses */
{ 0xD100000, 0x4000000, "str",   BT,REG12, MEM,   none,  none  },
{ 0xD100000, 0x4100000, "ldr",   BT,REG12, MEM,   none,  none  },
{ 0xD100000, 0x5000000, "str",   B, REG12, MEM,   none,  none  },
{ 0xD100000, 0x5100000, "ldr",   B, REG12, MEM,   none,  none  },
{ 0xE100000, 0x8000000, "stm",   M, MULTI, none,  none,  none  },
{ 0xE100000, 0x8100000, "ldm",   M, MULTI, none,  none,  none  },

/* A000000-BFFFFFF: branches */
{ 0xF000000, 0xA000000, "b",     0, BRCH,  none,  none,  none  },
{ 0xF000000, 0xB000000, "bl",    0, BRCH,  none,  none,  none  },

/* F000000-FFFFFFF: software interrupt */
{ 0xF000000, 0xF000000, "swi",   0, SWI,   none,  none,  none  },

/* Catch-all */
{ 0x0000000, 0x0000000, "???",   0, none,  none,  none,  none  },
};

static char *do_shift(char *out, uint32_t insn) {
    static const char shifts[4][4] = { "lsl", "lsr", "asr", "ror" };
    int shift = insn >> 7 & 31;
    int stype = insn >> 5 & 3;

    out = strcpy2(out, reg_name[insn & 15]);
    if (insn & 0x10) {
        // shift by register (for data processing only, not load/store)
        if (shift & 1) {
            *out++ = '?';
            return out;
        }
        out += sprintf(out, " %s %s", shifts[stype], reg_name[shift >> 1]);
    } else {
        // shift by immediate
        int shift = insn >> 7 & 31;
        int stype = insn >> 5 & 3;
        if (shift == 0) {
            if (stype == 0) {
                // lsl #0 is a no-op
                return out;
            } else if (stype == 3) {
                // ror #0
                return strcpy2(out, " rrx");
            } else {
                // lsr #0 and asr #0 act like shift of 32
                shift = 32;
            }
        }
        out += sprintf(out, " %s #%d", shifts[stype], shift);
    }
    return out;
}

static char *do_reglist(char *out, int regs) {
    int i;
    *out++ = '{';
    for (i = 0; i < 16; i++) {
        if (regs >> i & 1) {
            out = strcpy2(out, reg_name[i]);
            if (regs >> i & 2) {
                *out++ = '-';
                while (regs >> ++i & 1);
                out = strcpy2(out, reg_name[i-1]);
            }
            *out++ = ',';
        }
    }
    out[-1] = '}';
    *out = '\0';
    return out;
}

uint32_t disasm_arm_insn(uint32_t pc) {
    uint32_t *pc_ptr = virt_mem_ptr(pc, 4);
    if(!pc_ptr)
        return 0;

    return disasm_arm_insn2(pc, pc_ptr);
}

uint32_t disasm_arm_insn2(uint32_t pc, uint32_t *pc_ptr) {
    char buf[80];

    uint32_t insn = *pc_ptr;
    char *out = buf + sprintf(buf, "%08x: %08x\t", pc, insn);

    int i;

    const struct arm_insn *t = table;
    while ((insn & t->mask) != t->value)
        t++;

    out = strcpy2(out, t->name);

    switch (t->suffix) {
        case XY:
            *out++ = (insn & (1 << 5)) ? 't' : 'b';
            /* fallthrough */
        case Y:
            *out++ = (insn & (1 << 6)) ? 't' : 'b';
    }

    if (!(t->mask & 0xF0000000))
        out = strcpy2(out, condcode[insn >> 28]);

    switch (t->suffix) {
        case S:
            if (insn & (1 << 20)) *out++ = 's';
            break;
        case B:
            if (insn & (1 << 22)) *out++ = 'b';
            break;
        case BT:
            if (insn & (1 << 22)) *out++ = 'b';
            if (insn & (1 << 21)) *out++ = 't';
            break;
        case M:
            *out++ = (insn & (1 << 23)) ? 'i' : 'd';
            *out++ = (insn & (1 << 24)) ? 'b' : 'a';
            break;
        case H:
            if (insn & (1 << 6)) *out++ = 's';
            *out++ = (insn & (1 << 5)) ? 'h' : 'b';
            break;
        case D:
            *out++ = 'd';
            break;
        case L:
            if (insn & (1 << 22)) *out++ = 'l';
            break;
    }

    for (i = 0; i < 4 && (&t->op1)[i] != none; i++) {
        *out++ = i ? ',' : '\t';
        switch ((&t->op1)[i]) {
            case REG0:  out = strcpy2(out, reg_name[insn       & 15]); break;
            case REG8:  out = strcpy2(out, reg_name[insn >>  8 & 15]); break;
            case REG12: out = strcpy2(out, reg_name[insn >> 12 & 15]); break;
            case REG16: out = strcpy2(out, reg_name[insn >> 16 & 15]); break;
            case SHFOP:
                if (!(insn & (1 << 25))) {
                    out = do_shift(out, insn);
                    break;
                }
                /* fallthrough */
            case IMMED: {
                uint32_t imm = insn & 255, shift = insn >> 7 & 30;
                out += sprintf(out, "%08x", imm >> shift | imm << (32 - shift));
                break;
            }
            case PSR:
                *out++ = (insn & (1 << 22)) ? 's' : 'c';
                *out++ = 'p'; *out++ = 's'; *out++ = 'r';
                if (~insn >> 16 & 15) {
                    *out++ = '_';
                    if (insn & (1 << 19)) *out++ = 'f';
                    if (insn & (1 << 18)) *out++ = 's';
                    if (insn & (1 << 17)) *out++ = 'x';
                    if (insn & (1 << 16)) *out++ = 'c';
                }
                break;
            case MEM:
                *out++ = '[';
                out = strcpy2(out, reg_name[insn >> 16 & 15]);
                if ((insn & 0x32F0000) == 0x10F0000) {
                    // immediate, offset mode, PC relative
                    int addr = insn & 0xFFF;
                    if (!(insn & (1 << 23))) addr = -addr;
                    addr += pc + 8;
                    out -= 2;
                    out += sprintf(out, "%08x]", addr);
                    uint32_t *ptr = virt_mem_ptr(addr, 4);
                    if (ptr)
                        out += sprintf(out, " = %08x", *ptr);
                } else {
                    if (!(insn & (1 << 24))) // post-index
                        *out++ = ']';
                    *out++ = ' ';
                    *out++ = (insn & (1 << 23)) ? '+' : '-';
                    *out++ = ' ';
                    if (insn & (1 << 25)) {
                        // register offset
                        if (insn & 0x10) {
                            // register shifted by register not allowed
                            *out++ = '?';
                        } else {
                            out = do_shift(out, insn);
                        }
                    } else {
                        // immediate offset
                        if ((insn & 0xFFF) || !(insn & (1 << 23)))
                            out += sprintf(out, "%03x", insn & 0xFFF);
                        else
                            // don't display an added offset of 0
                            out -= 3;
                    }
                    if (insn & (1 << 24)) { // pre-index
                        *out++ = ']';
                        if (insn & (1 << 21)) // writeback
                            *out++ = '!';
                    }
                }
                break;
            case MEMH:
                *out++ = '[';
                out = strcpy2(out, reg_name[insn >> 16 & 15]);
                if (!(insn & (1 << 24))) // post-index
                    *out++ = ']';
                *out++ = ' ';
                *out++ = (insn & (1 << 23)) ? '+' : '-';
                *out++ = ' ';
                if (insn & (1 << 22)) {
                    // immediate offset
                    uint32_t imm = (insn & 0x0F) | (insn >> 4 & 0xF0);
                    if (imm || !(insn & (1 << 23)))
                        out += sprintf(out, "%03x", imm);
                    else
                        // don't display an added offset of 0
                        out -= 3;
                } else {
                    // register offset
                    out = strcpy2(out, reg_name[insn & 15]);
                }
                if (insn & (1 << 24)) { // pre-index
                    *out++ = ']';
                    if (insn & (1 << 21)) // writeback
                        *out++ = '!';
                } else { // post-index
                    // writeback assumed, setting W bit is invalid.
                    if (insn & (1 << 21))
                        *out++ = '?';
                }
                break;
            case MEM16:
                out += sprintf(out, "[%s]", reg_name[insn >> 16 & 15]);
                break;
            case MULTI:
                out = strcpy2(out, reg_name[insn >> 16 & 15]);
                if (insn & (1 << 21)) // Writeback
                    *out++ = '!';
                *out++ = ',';
                out = do_reglist(out, insn & 0xFFFF);
                if (insn & (1 << 22)) // Load PSR or force user mode
                    *out++ = '^';
                break;
            case BRCH:
                out += sprintf(out, "%08x", pc + 8 + ((int)insn << 8 >> 6));
                break;
            case BRCHT:
                out += sprintf(out, "%08x", pc + 8 + ((int)insn << 8 >> 6 | (insn >> 23 & 2)));
                break;
            case COPR:
                out += sprintf(out, "p%d", insn >> 8 & 15);
                break;
            case CDP:
                out += sprintf(out, "%d,c%d,c%d,c%d,%d", insn >> 20 & 15,
                               insn >> 12 & 15, insn >> 16 & 15, insn & 15, insn >> 5 & 7);
                break;
            case MRC:
                out += sprintf(out, "%d,%s,c%d,c%d,%d", insn >> 21 & 7,
                               reg_name[insn >> 12 & 15], insn >> 16 & 15, insn & 15, insn >> 5 & 7);
                break;
            case MRRC:
                out += sprintf(out, "%d,%s,%s,c%d", insn >> 4 & 15,
                               reg_name[insn >> 12 & 15], reg_name[insn >> 16 & 15], insn & 15);
                break;
            case MEMC:
                out += sprintf(out, "c%d,[%s", insn >> 12 & 15,
                               reg_name[insn >> 16 & 15]);
                if (!(insn & (1 << 24))) { // Post-indexed or unindexed
                    *out++ = ']';
                    if (!(insn & (1 << 21))) {
                        // Unindexed addressing
                        if (!(insn & (1 << 23)))
                            *out++ = '?';
                        out += sprintf(out, ",{%02x}", insn & 0xFF);
                        break;
                    }
                }
                if (!(insn & (1 << 23)) || insn & 0xFF)
                    out += sprintf(out, " %c %03x",
                                   (insn & (1 << 23)) ? '+' : '-',
                                   (insn & 0xFF) << 2);
                if (insn & (1 << 24)) { // Pre-indexed or offset
                    *out++ = ']';
                    if (insn & (1 << 21)) // Writeback (pre-indexed)
                        *out++ = '!';
                }
                break;
            case BKPT:
                out += sprintf(out, "%04x", (insn >> 4 & 0xFFF0) | (insn & 0x0F));
                break;
            case SWI:
                out += sprintf(out, "%06x", insn & 0xFFFFFF);
                break;
        }
    }
    *out = '\0';
    gui_debug_printf(buf);
    gui_debug_printf("\n");
    return 4;
}

uint32_t disasm_thumb_insn(uint32_t pc) {
    char buf[80];
    uint16_t *pc_ptr = virt_mem_ptr(pc, 2);
    if (!pc_ptr)
        return 0;

    uint16_t insn = *pc_ptr;
    char *out = buf + sprintf(buf, "%08x: %04x    \t", pc, insn);

    if (insn < 0x1800) {
        static const char name[][4] = { "lsl", "lsr", "asr" };
        sprintf(out, "%s\tr%d,r%d,%02x", name[insn >> 11],
                insn & 7, insn >> 3 & 7, insn >> 6 & 31);
    } else if (insn < 0x2000) {
        sprintf(out, "%s\tr%d,r%d,%s%d",
                (insn & 0x0200) ? "sub" : "add",
                insn & 7, insn >> 3 & 7,
                (insn & 0x400) ? "" : "r", insn >> 6 & 7);
    } else if (insn < 0x4000) {
        static const char name[][4] = { "mov", "cmp", "add", "sub" };
        sprintf(out, "%s\tr%d,%02x", name[insn >> 11 & 3],
                insn >> 8 & 7, insn & 0xFF);
    } else if (insn < 0x4400) {
        static const char name[][4] = {
            "and", "eor", "lsl", "lsr", "asr", "adc", "sbc", "ror",
            "tst", "neg", "cmp", "cmn", "orr", "mul", "bic", "mvn"
        };
        sprintf(out, "%s\tr%d,r%d", name[insn >> 6 & 15], insn & 7, insn >> 3 & 7);
    } else if (insn < 0x4700) {
        static const char name[][4] = { "add", "cmp", "mov" };
        int rd = (insn & 7) | (insn >> 4 & 8);
        int rn = insn >> 3 & 15;
        if (!((rd | rn) & 8))
            goto invalid;
        sprintf(out, "%s\t%s,%s", name[insn >> 8 & 3], reg_name[rd], reg_name[rn]);
    } else if (insn < 0x4800) {
        if (insn & 7)
            goto invalid;
        sprintf(out, "%s\t%s", (insn & 0x80) ? "blx" : "bx", reg_name[insn >> 3 & 15]);
    } else if (insn < 0x5000) {
        int addr = ((pc + 4) & -4) + ((insn & 0xFF) << 2);
        out += sprintf(out, "ldr\tr%d,[%08x]", insn >> 8 & 7, addr);
        uint32_t *ptr = virt_mem_ptr(addr, 4);
        if (ptr)
            sprintf(out, " = %08x", *ptr);
    } else if (insn < 0x6000) {
        static const char name[][6] = {
            "str", "strh", "strb", "ldrsb",
            "ldr", "ldrh", "ldrb", "ldrsh"
        };
        sprintf(out, "%s\tr%d,[r%d + r%d]", name[insn >> 9 & 7],
                insn & 7, insn >> 3 & 7, insn >> 6 & 7);
    } else if (insn < 0x9000) {
        static const char name[][5] = { "str", "ldr", "strb", "ldrb", "strh", "ldrh" };
        int type = (insn - 0x6000) >> 11;
        sprintf(out, "%s\tr%d,[r%d + %03x]", name[type],
                insn & 7, insn >> 3 & 7,
                (insn >> 6 & 31) << (0x12 >> (type & 6) & 3));
    } else if (insn < 0xA000) {
        sprintf(out, "%s\tr%d,[sp + %03x]",
                (insn & 0x800) ? "ldr" : "str",
                insn >> 8 & 7, (insn & 0xFF) << 2);
    } else if (insn < 0xB000) {
        sprintf(out, "add\tr%d,%s,%03x",
                insn >> 8 & 7,
                (insn & 0x800) ? "sp" : "pc", (insn & 0xFF) << 2);
    } else if (insn < 0xB100) {
        sprintf(out, "%s\tsp,%03x",
                (insn & 0x80) ? "sub" : "add", (insn & 0x7F) << 2);
    } else if (insn < 0xB400) {
        goto invalid;
    } else if (insn < 0xB600) {
        out = strcpy2(out, "push\t");
        do_reglist(out, (insn & 0xFF) | (insn & 0x100) << (14 - 8));
    } else if (insn < 0xBC00) {
        goto invalid;
    } else if (insn < 0xBE00) {
        out = strcpy2(out, "pop\t");
        do_reglist(out, (insn & 0xFF) | (insn & 0x100) << (15 - 8));
    } else if (insn < 0xBF00) {
        sprintf(out, "bkpt\t%02x", insn & 0xFF);
    } else if (insn < 0xC000) {
        goto invalid;
    } else if (insn < 0xD000) {
        out += sprintf(out, "%smia\tr%d!,", (insn & 0x800) ? "ld" : "st", insn >> 8 & 7);
        do_reglist(out, insn & 0xFF);
    } else if (insn < 0xDE00) {
        sprintf(out, "b%s\t%08x", condcode[insn >> 8 & 15], pc + 4 + ((int8_t)insn << 1));
    } else if (insn < 0xDF00) {
invalid:
        sprintf(out, "???");
    } else if (insn < 0xE000) {
        sprintf(out, "swi\t%02x", insn & 0xFF);
    } else if (insn < 0xE800) {
        sprintf(out, "b\t%08x", pc + 4 + ((int32_t)insn << 21 >> 20));
    } else if (insn < 0xF000) {
        sprintf(out, "(blx\tlr + %03x)", (insn & 0x7FF) << 1);
    } else if (insn < 0xF800) {
        int32_t target = (int32_t)insn << 21 >> 9;
        /* Check next instruction to see if this is part of a BL or BLX pair */
        pc_ptr = virt_mem_ptr(pc + 2, 2);
        if (pc_ptr && ((insn = *pc_ptr) & 0xE800) == 0xE800) {
            /* It is; show both instructions combined as one */
            target += pc + 4 + ((insn & 0x7FF) << 1);
            if (!(insn & 0x1000)) target &= ~3;
            sprintf(out - 5, "%04x\t%s\t%08x", insn,
                    (insn & 0x1000) ? "bl" : "blx", target);
            gui_debug_printf("%s\n", buf);
            return 4;
        }
        sprintf(out, "(add\tlr,pc,%08x)", target);
    } else {
        sprintf(out, "(bl\tlr + %03x)", (insn & 0x7FF) << 1);
    }
    gui_debug_printf("%s\n", buf);
    return 2;
}
